package common.dal;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import common.logger.CompositeLogger;
import common.logger.Logger.LogLevel;

/**
 * A pool of cached connections to the Database
 * 
 * @author Gal Lalouche
 */
class ConnectionPool {
	// private final String url;
	// private final String user;
	// private final String password;
	private BlockingQueue<Connection>	connections;

	/**
	 * Creates a new connection pool
	 * 
	 * @param maxConnections The maximum amount of concurrent connections allowed.
	 * @param props The properties of the DB - should contain:<br>
	 *        <li>url<li>driver<li>dbName<li>username<li>password
	 * @throws SQLException
	 */
	public ConnectionPool(int maxConnections, Properties props) throws SQLException {
		if (maxConnections <= 0) {
			throw new ArithmeticException("maxConnections must be a positive integer");
		}
		String driver = props.getProperty("driver");
		String url = props.getProperty("url");
		String dbName = props.getProperty("dbName");
		String username = props.getProperty("username");
		String password = props.getProperty("password");

		try {
			Class.forName(driver);
		} catch (ClassNotFoundException e) {
			throw new AssertionError(e);
		}

		connections = new ArrayBlockingQueue<>(maxConnections);

		for (int i = 0; i < maxConnections; i++) {
			@SuppressWarnings("resource")
			java.sql.Connection tempCon = DriverManager.getConnection(url + dbName, username, password);
			tempCon.setAutoCommit(false);
			boolean result = connections.add(tempCon);
			assert result;
		}
	}

	/**
	 * @return A connection to the database; is a blocking call
	 */
	public Connection getConnection() {
		try {
			Connection $ = connections.take();
			return $;
		} catch (InterruptedException e) {
			throw new AssertionError(e);
		}
	}

	/**
	 * Commits all changes done on the connection
	 * 
	 * @param connection
	 * @return <code>true</code> if changes we successfully commit, <code>false</code> if changes were rolled back
	 * @throws SQLException
	 */
	public boolean commit(Connection connection) throws SQLException {
		if (connection == null) {
			throw new IllegalArgumentException("connection is null");
		}

		connection.commit();
		try {
			connection.commit();
			CompositeLogger.getInstance().log(LogLevel.DEBUG, "commited successfully");
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
			CompositeLogger.getInstance().log(LogLevel.WARNING, "failed to commit");
			connection.rollback();
			return false;
		} finally {
			boolean result = connections.add(connection);
			assert result;
		}
	}

	@Deprecated
	/**
	 * commits all changes that were for some reason not committed.<br>
	 * <i>Used for debugging</i>
	 * @throws SQLException
	 */
	public void commitAll() throws SQLException {
		for (Connection conn : connections) {
			conn.commit();
		}
	}

	@Override
	protected void finalize() throws Throwable {
		closePool();
	}

	/**
	 * Closes all connections in the pool
	 * 
	 * @throws SQLException
	 */
	public void closePool() throws SQLException {
		for (Connection con : connections) {
			con.close();
		}
		CompositeLogger.getInstance().log(LogLevel.INFO, "closing the pool");
		connections = null;
	}

	/**
	 * Rollbacks all changes done on the connection
	 * 
	 * @param connection
	 */
	public void rollBack(Connection connection) {
		if (connection == null) {
			throw new IllegalArgumentException("connection is null");
		}

		try {
			connection.commit(); // this is hack to fix a bug
			// connection.rollback();
			CompositeLogger.getInstance().log(LogLevel.DEBUG, "rolledback successfully");
		} catch (SQLException e) {
			CompositeLogger.getInstance().log(LogLevel.ERROR, "rollback failed");
			throw new AssertionError(e);
		} finally {
			connections.add(connection);
		}
	}
}
